
/*
 * 企通数易通 - 用友YonSuite登录并连接U8数据库节点
 * 功能：
 * 1. 支持连接MySQL数据库
 * 2. 执行SQL查询语句
 * 3. 处理查询结果并返回
 * 4. 支持错误处理和异常捕获
 * Author: 上海企通数字科技有限公司 www.cotong.com Karon.Luo
 * Date: 2025-07-30
 * Version: 1.0.0
 */
module.exports = function (RED) {

	/**
	 * 生成用友Yonsuite API签名
	 * @param {string} appKey - 应用的密钥ID
	 * @param {string} appSecret - 应用的密钥
	 * @param {number} timestamp - 当前时间戳
	 * @returns {string} 生成的签名，经过URI编码处理
	 */
	async function YonSuiteGenSignature(appKey, appSecret, timestamp) {
		// 引入crypto-js的Base64编码模块
		const BASE64 = require('crypto-js/enc-base64');
		// 引入crypto-js的HMAC-SHA256哈希算法模块
		const HMACSHA256 = require('crypto-js/hmac-sha256');
		// 拼接签名前的字符串，格式为 "appKey{appKey}timestamp{timestamp}"
		const pre_signature = "appKey" + appKey + "timestamp" + timestamp;
		// 使用HMAC-SHA256算法对拼接字符串进行签名，然后进行Base64编码
		const signature = encodeURI(BASE64.stringify(HMACSHA256(pre_signature, appSecret)));
		// 返回经过URI编码后的签名
		return signature;
	}

	/**
	 * 通过签名获取用友Yonsuite API访问令牌
	 * @param {string} signature - 生成的签名，用于验证请求合法性
	 * @param {string} ysapiurl - 用友Yonsuite API的基础URL
	 * @param {string} appKey - 应用的密钥ID
	 * @param {number} timestamp - 当前时间戳
	 * @returns {Promise<Object>} 包含API响应数据的Promise对象
	 */
	async function GetYonSuiteToken(signature, ysapiurl, appKey, timestamp) {
		// 拼接完整的API请求URL，包含所需参数
		var url = ysapiurl + "?appKey=" + appKey + "&timestamp=" + timestamp + "&signature=" + signature;
		// 引入axios库用于发送HTTP请求
		const axios = require('axios');
		// 发送GET请求获取访问令牌
		const response = await axios.get(url, { headers: { "Content-Type": "application/json" } });
		// 返回响应数据
		return response.data;
	}

	/**
	 * 构建成功结果对象
	 * @param {any} db_result - 数据库查询结果
	 * @param {Object} config - 节点配置信息
	 * @param {string} yonsuite_access_token - YonSuite访问令牌
	 * @param {string} signature - 请求签名
	 * @returns {Object} 包含成功信息的结果对象
	 */
	async function BuildOKResult(db_result, config, yonsuite_access_token, signature) {
		return {
			output: 'success',
			result: db_result,       // 查询结果
			config: config,         // 节点配置
			error: NaN,             // 无错误
			yonsuite_access_token: yonsuite_access_token,     // token字段
			signature: signature, // 签名
			result_schema: await ConvertJSONToSchame(db_result),
		}
	}

	/**
	 * 构建错误结果对象
	 * @param {Object} config - 节点配置信息
	 * @param {Error|string} err - 错误信息对象或错误消息字符串
	 * @param {string} yonsuite_access_token - YonSuite访问令牌
	 * @param {string} signature - 请求签名
	 * @returns {Object} 包含错误信息的结果对象
	 */
	async function BuildErrorResult(config, err, yonsuite_access_token, signature) {
		return {
			output: 'error',          // 输出状态：错误
			result: [],               // 查询结果：空数组
			config: config,           // 节点配置信息
			error: err,               // 错误信息
			yonsuite_access_token: yonsuite_access_token,     // YonSuite访问令牌
			signature: signature,     // API请求签名
		}
	}

	/**
	 * 将JSON数据转换为Schema格式
	 * @param {Object} jsonData - 需要转换的JSON数据
	 * @returns {Object} 转换后的Schema对象
	 */
	async function ConvertJSONToSchame(jsonData) {
		// 使用 generate-schema 库将JSON数据转换为 Schame
		const generateSchema = require('generate-schema');
		return generateSchema.json(jsonData);
		
	}

	/**
	 * CotongYSToU8Login 节点构造函数
	 * @param {Object} config - 节点配置参数
	 */
	function CotongYSToU8Login(config) {
		// 创建Node-RED节点
		RED.nodes.createNode(this, config);
		var node = this;
		// 生成用友Yonsuite API 签名

		// 保存原始密码，避免在日志中暴露
		const oriPassword = config.dbpassword;
		// config.dbpassword = "******"; // 隐藏密码（可选，根据需求启用）
		/**
		 * 输入事件处理函数
		 * @param {Object} msg - 输入消息对象
		 */
		this.on('input', async function (msg) {
			/**
			 * 用友YonSuite API访问令牌获取逻辑
			 * 实现带重试机制的令牌请求
			 */
			var yonsuite_access_token = "未获得访问令牌"; // 初始化令牌默认值
			var retryTimes = 0; // 重试计数器
			var signature = ""; // 签名变量

			// 循环获取令牌，支持自动重试
			while (true) {
				const timestamp = Date.now(); // 生成当前时间戳
				// 生成API签名（使用应用密钥、密钥和时间戳）
				signature = await YonSuiteGenSignature(config.ysappkey, config.ysappsecret, timestamp);
				// 调用API获取访问令牌
				const res = await GetYonSuiteToken(signature, config.ysapiurl, config.ysappkey, timestamp);

				// 处理API响应
				if (res.code == "500" && retryTimes < 20) {
					// 服务器错误(500)且重试次数<20时，继续重试
					retryTimes++;
					continue;
				} else if (res.code == "00000") {
					// 请求成功(00000)时，提取访问令牌并退出循环
					yonsuite_access_token = res.data.access_token;
					break;
				}
				// 其他情况(如参数错误)直接退出循环
				break;
			}

			if (msg.query && msg.query != "") {
				config.query = msg.query;
			}
			// 检查查询语句是否为空
			if (config.query == "") {
				throw "query is empty"; // 查询语句为空时抛出异常
			}
			var sql;
			// 业务处理逻辑
			if (config.dbcategory == "mysql") {

				try {
					// 导入mysql2/promise模块
					sql = require('mysql2/promise');
					// 创建数据库连接
					const connection = await sql.createConnection({
						host: config.dbhost,     // 数据库主机
						user: config.dbuser,     // 数据库用户名
						password: oriPassword,   // 数据库密码
						database: config.database, // 数据库名称
						port: parseInt(config.dbport), // 数据库端口
					});
					// 连接数据库
					connection.connect();
					// 如果消息中包含查询语句，则使用消息中的查询
					// 执行查询
					const [dbResults] = await connection.query(config.query);
					// 构造成功响应
					msg.payload = await BuildOKResult(dbResults, config, yonsuite_access_token, signature);
					// 关闭数据库连接
					connection.end();
				} catch (e) {
					// 处理异常情况
					msg.payload = await BuildErrorResult(config, e, yonsuite_access_token, signature);
				}
				// 发送消息

			} else if (config.dbcategory == "mssql") {
				try {
					// 导入mssql模块
					sql = require('mssql');
					const db_config = {
						user: config.dbuser,
						password: oriPassword,
						server: config.dbhost, // 或 IP 地址
						database: config.database,
						port: parseInt(config.dbport), // 可选，默认 1433
						options: {
							encrypt: false, // 如果是 Azure SQL，设为 true
							// trustServerCertificate: true, // 接受自签名证书（开发环境可用）
							// enableArithAbort: true,
							// instanceName: 'SQLEXPRESS' // 如果使用命名实例，例如 SQLEXPRESS
						}
					};
					const pool = await sql.connect(db_config);
					const request = pool.request();
					const results = await request.query(config.query);
					await pool.close();
					// 构造成功响应
					msg.payload = await BuildOKResult(results, config, yonsuite_access_token, signature);
				} catch (e) {
					msg.payload = await BuildErrorResult(config, e, yonsuite_access_token, signature);
				}
			}
			else {
				// 其他数据库类型暂未实现
				msg.payload = {
					output: 'error',
					error: 'Database category not supported: ' + config.dbcategory,
					config: config,
					token: NaN,
					results: []
				};
			}
			node.send(msg);
		});

		// config.dbpassword = oriPassword; // 恢复密码（可选）
	}

	// 注册节点类型
	// 注意：节点类型名称必须唯一，不能重复注册
	RED.nodes.registerType("cotong-ys2u8-login", CotongYSToU8Login);
}
